import RPi.GPIO as GPIO
import numpy as np
import time
import datetime as dt
# init func
def init(pin_list):
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(pin_list, GPIO.OUT)
    GPIO.output(pin_list, GPIO.LOW)

# util func
def pulse(pin, step, is_test=True):
    if step == 0:
        return
    if is_test:
        print('virtual shake at pin-%s, duration = %f sec' % (pin, step))
    else:
        print('real shake at pin-%s, duration = %f sec' % (pin, step))
        GPIO.output(pin, GPIO.HIGH)
        time.sleep(step)
        GPIO.output(pin, GPIO.LOW)

def s2date(s):
    return dt.date(int(s[:4]), int(s[4:6]), int(s[7:]))

def is_on_time(start_date_s, stop_date_s):
    fmt = '%Y-%m-%d-%H.%M.%S'
    curr_tm = time.localtime(time.time())
    start_tm = time.strptime(start_date_s, fmt)
    stop_tm = time.strptime(stop_date_s, fmt)
    if curr_tm > start_tm and curr_tm < stop_tm:
        return 1
    else:
        if curr_tm < start_tm:
            diff_sec = time.mktime(start_tm) - time.mktime(curr_tm)
            print('will start after %d sec' % int(diff_sec))
            return 0
        else:
            print('Already out of date.')
            return -1

def update_duration_list(interv_wait, stimuli_list, motor_num, timer):
    duration_list = np.zeros(motor_num)
    for i, stimuli in enumerate(stimuli_list):
        if interv_wait > 0 and timer % interv_wait == 0: # channel-i, interv/stimuli
            duration_list[i] = stimuli
    return duration_list

def generate_stimuli(motor_num, interv_list_define, stimuli_list_define):
    interv_list = interv_list_define
    if motor_num > len(interv_list):
        interv_list.extend(np.zeros(motor_num - len(interv_list)))
    elif motor_num < len(interv_list):
        interv_list = interv_list[:motor_num]

    stimuli_list = stimuli_list_define
    if motor_num > len(stimuli_list):
        stimuli_list.extend(np.zeros(motor_num - len(stimuli_list)))
    elif motor_num < len(stimuli_list):
        stimuli_list = stimuli_list[:motor_num]
    
    return interv_list, stimuli_list

def batch_pulse(pin_list, motor_list, duration_list, step_num, is_test):
    print('shake_cycle = %d and among them step_len = %f sec' % (step_num, duration_list[0]))
    for i in range(step_num):
        [pulse(pin_list[motor-1], duration, is_test) for motor, duration in zip(motor_list, duration_list)]

if __name__=='__main__':
    # settings
    is_test = False                                 # on test mode, no shake, just output messages
    start_date_s = '2020-01-11-21.00.00'            # start date
    stop_date_s  = '2020-01-12-09.00.00'            # stop date
    motor_list   = list(range(1,17))                # 1-based indices
    shake_step   = 0.2                              # shake duration per each step (sec)
    step_num     = 8                                # shake step number per each cycle
    shake_increment_num = 8                         # the number of stage when shake frequency increase
    duration_list= [shake_step  ] * 16              # stimuli duration, channel-specific
    pulse_wait   = shake_step*step_num * 16 + 20.   # interval between stimuli, channel-specific
    step_num_increment = 1                          # gradual increment of stimuli duration
    
    pin_list = [2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
    motor_num = len(motor_list)
    init(pin_list)
    timer = 1
    start_tm = time.time() # initial time point
    while(1):
        time_status = is_on_time(start_date_s, stop_date_s)
        if time_status == 1:
            #duration_list = update_duration_list(interv_wait, stimuli_list, motor_num, timer)
            batch_pulse(pin_list, motor_list, duration_list, step_num, is_test)
            pulse_total_len = time.time() - start_tm - timer + 1
            #print(pulse_wait)
            if pulse_wait > pulse_total_len:
                wait_debris = pulse_wait - pulse_total_len
                print('wait interval = %f sec' % wait_debris)
                time.sleep(wait_debris)
            else:
                sec_num = int(pulse_total_len)
                debris = pulse_total_len - sec_num
                time.sleep(pulse_wait - debris)
                timer += sec_num
            timer += 1
            if timer % int((720 * 60 / pulse_wait) / shake_increment_num) == 0:
                step_num += step_num_increment
        elif time_status == 0:
            time.sleep(1)
        else:
            break
